from typing import Any, Callable, List, Literal, Optional

from typing_extensions import Self

from ..binding import BindableProperty
from ..element import Element
from ..events import (
    GenericEventArguments,
    KeyboardAction,
    KeyboardKey,
    KeyboardModifiers,
    KeyEventArguments,
    handle_event,
)


class Keyboard(Element, component='keyboard.js'):
    active = BindableProperty()

    def __init__(self,
                 on_key: Optional[Callable[..., Any]] = None, *,
                 active: bool = True,
                 repeating: bool = True,
                 ignore: List[Literal['input', 'select', 'button', 'textarea']] =
                     ['input', 'select', 'button', 'textarea'],  # noqa: B006
                 ) -> None:
        """Keyboard

        Adds global keyboard event tracking.

        The `on_key` callback receives a `KeyEventArguments` object with the following attributes:

        - `sender`: the `Keyboard` element
        - `client`: the client object
        - `action`: a `KeyboardAction` object with the following attributes:
            - `keydown`: whether the key was pressed
            - `keyup`: whether the key was released
            - `repeat`: whether the key event was a repeat
        - `key`: a `KeyboardKey` object with the following attributes:
            - `name`: the name of the key (e.g. 'a', 'Enter')
            - `code`: the code of the key (e.g. 'KeyA', 'Enter')
            - `location`: the location of the key (0 for standard keys, 1 for left keys, 2 for right keys, 3 for numpad keys)
        - `modifiers`: a `KeyboardModifiers` object with the following attributes:
            - `alt`: whether the alt key was pressed
            - `ctrl`: whether the ctrl key was pressed
            - `meta`: whether the meta key was pressed
            - `shift`: whether the shift key was pressed

        For convenience, the `KeyboardKey` object also has the following properties:
            - `is_cursorkey`: whether the key is a cursor (arrow) key
            - `number`: the integer value of a number key (0-9, `None` for other keys)
            - `backspace`, `tab`, `enter`, `shift`, `control`, `alt`, `pause`, `caps_lock`, `escape`, `space`,
              `page_up`, `page_down`, `end`, `home`, `arrow_left`, `arrow_up`, `arrow_right`, `arrow_down`,
              `print_screen`, `insert`, `delete`, `meta`,
              `f1`, `f2`, `f3`, `f4`, `f5`, `f6`, `f7`, `f8`, `f9`, `f10`, `f11`, `f12`: whether the key is the respective key

        :param on_key: callback to be executed when keyboard events occur.
        :param active: boolean flag indicating whether the callback should be executed or not (default: `True`)
        :param repeating: boolean flag indicating whether held keys should be sent repeatedly (default: `True`)
        :param ignore: ignore keys when one of these element types is focussed (default: `['input', 'select', 'button', 'textarea']`)
        """
        super().__init__()
        self._key_handlers = [on_key] if on_key else []
        self.active = active
        self._props['events'] = ['keydown', 'keyup']
        self._props['repeating'] = repeating
        self._props['ignore'] = ignore[:]
        self.on('key', self._handle_key)

    def _handle_key(self, e: GenericEventArguments) -> None:
        if not self.active:
            return

        action = KeyboardAction(
            keydown=e.args['action'] == 'keydown',
            keyup=e.args['action'] == 'keyup',
            repeat=e.args['repeat'],
        )
        modifiers = KeyboardModifiers(
            alt=e.args['altKey'],
            ctrl=e.args['ctrlKey'],
            meta=e.args['metaKey'],
            shift=e.args['shiftKey'],
        )
        key = KeyboardKey(
            name=e.args['key'],
            code=e.args['code'],
            location=e.args['location'],
        )
        arguments = KeyEventArguments(
            sender=self,
            client=self.client,
            action=action,
            modifiers=modifiers,
            key=key,
        )
        for handler in self._key_handlers:
            handle_event(handler, arguments)

    def on_key(self, handler: Callable[..., Any]) -> Self:
        """Add a callback to be invoked when keyboard events occur."""
        self._key_handlers.append(handler)
        return self
